/*
 * Copyright © 2008 University of Houston
 * All rights reserved.
 */

#include <stdio.h>
#include <math.h>
#include <string>
#include "algorithm.h"
#include "filter.h"
#include "util.h"

using namespace std;

/*
 * Start Algorithm Class
 */
void algorithm::initializeVars ()
{
	lastPktTime = PKT_NO_VAL;
	pktCount = 0;
}

void algorithm::initializeResultList (int sessID, string logfile)
{
	resultList.sessionNumber = sessID;
	resultList.filename = logfile;
}

void algorithm::startAnalysis (timedHeader *th, int frs)
{
	latestPkt = th;
	filtRetStatus = frs;
	
	// Return if told to do nothing
	if (doNothing())
		return;

	// Get timestamp of latest packet
	ts = ttod(th->getTimestamp());

	// Update lastPktTime
	if (lastPktTime == PKT_NO_VAL) 
	{
		lastPktTime = ts;
		return;
	}

	if (doAnalysis())
	{
		pktCount++;
		analyze();
	}
	else 
		finishAnalysis();
}

void algorithm::analyze()
{
	finishAnalysis();
}

void algorithm::finishAnalysis ()
{
	lastPktTime = ts;
}

algorithm* algorithm::getAlg(int alg)
{
	algorithm *p = NULL;
		
	switch(alg)
	{
		case 0:	p = new algorithm(); 	break;
		case 1: p = new timeDiff(); 	break;
		case 2: p = new avgDelay(); 	break;
		case 3: p = new stdDevDelay(); 	break;
		case 4: p = new uberFilter(); 	break;
	}
	
	if (p != NULL)
		VDEBUG("Algorithm Created: " << p->getName())
	else if( p == NULL )
		ERROR("Invalid algorithm ID specified: " << alg)
	
	return p;
}

string algorithm::getAlgName(int algID)
{
	algorithm 	*temp = algorithm::getAlg(algID);
	string		retVal = temp->getName();
	delete		temp;
	
	return 		retVal;
}

analyzerResultList* algorithm::getResultList()
{
	return &resultList;
}

/*
 * Start timeDiff Alg
 */
void timeDiff::initializeVars()
{
	algorithm::initializeVars();
}

void timeDiff::analyze()
{
	interval = ts - lastPktTime;

	DEBUG(pktCount << ".Time Diff: " << interval)

	resultList.push_back(interval);

	finishAnalysis();
}

/*
 * Start avgDelay Alg
 */
void avgDelay::initializeVars()
{
	algorithm::initializeVars();
	avgDelay = totalDelay = 0;
}

void avgDelay::analyze()
{
	totalDelay += ts - lastPktTime;
	avgDelay = totalDelay / pktCount;

	DEBUG(pktCount << "Avg Delay: " << avgDelay)
	
	resultList.push_back(avgDelay);

	finishAnalysis();
}

/*
 * Start stdDevDelay Alg
 */
void stdDevDelay::initializeVars()
{
	algorithm::initializeVars();
	sumOfValues = sumOfSquaredValues = 0;
}

void stdDevDelay::analyze()
{
	interval = ts - lastPktTime;

	sumOfValues += interval;
	sumOfSquaredValues += interval * interval;

	stdDev = sqrt(pktCount * sumOfSquaredValues - (sumOfValues*sumOfValues)) / pktCount;
	
	DEBUG(pktCount << "StdDev: " << stdDev)
	
	resultList.push_back(stdDev);

	finishAnalysis();
}

/*
 * Start uberFilter
 */
void uberFilter::initializeVars()
{
	algorithm::initializeVars();

	// Create Filters
	nearHop 	= filter::getFilter(1); // Nearest Hop Filter
	userDelay	= filter::getFilter(2);	// UserDelay Filter
	command		= filter::getFilter(5); // Command Filter

	// Initialize Filters
	nearHop->initializeVars();
	userDelay->initializeVars();
	command->initializeVars();
	
	// User Parameters
	lowerThreshold = 12;
	upperThreshold = 15;
	stability = 500;
	sensitivity = 3;
	speedMultiplier = 3;
	rttCutOff = 3;

	typingSpeed = .5; // Initial guess
	lastCharTime = lastCmdTime = -1;
	totalCharTime = 0;
	charCount = cmdCount = 0;
	
	nearHopRtt = 0;

	// Avg Num Hop Estimation Vars
	avgNumHops = 3;
	totalNumHops = guessCount = 0;

	suspicion = 0;
}

/*
 * All analysis is done in this filter and nothing
 * is passed to any algorithm.
 */
void uberFilter::analyze()
{
	// Update the filters
	int nearHopStatus 	= 	nearHop->update(latestPkt);
	int userStatus		=	userDelay->update(latestPkt);
	int commandStatus	= 	command->update(latestPkt);
	
	interval = ts - lastPktTime;

	// NearHop RTT packet Detected
	if (nearHopStatus == ALG_DO_ANALYSIS)
	{
		nearHopRtt = ((nearestHopFilter*)nearHop)->getRtt();
	}

	// Char Detected
	if (userStatus == ALG_DO_ANALYSIS)
	{
		VDEBUG("--CHARACTER--")
		
		// Do Important Analysis
		if (lastCmdTime != -1)
		{
			twitchTime = ts - lastCmdTime;
			guessRtt = twitchTime - typingSpeed;
			guessNumHops = guessRtt;
			//guessNumHops = guessRtt / nearHopRtt;
			
			if (guessRtt < rttCutOff && guessRtt > 0)
			{
				totalNumHops += guessNumHops;
				guessCount++;
				avgNumHops = totalNumHops / guessCount;
				
				printStats();

				DEBUG("Uber: Pushed " << avgNumHops * nearHopRtt * nearHopRtt)

				resultList.push_back(avgNumHops * nearHopRtt);

				if (pktCount > stability) 
				{
					calculateSuspicion();
					DEBUG("\tSuspicion: " << suspicion)
				}
			}
			lastCmdTime = -1;
		}
		updateTypeSpeed();
			
		lastCharTime = ts;
	}

	// New Command Detected
	if (commandStatus == ALG_DO_ANALYSIS)
	{
		VDEBUG("--COMMAND--")
		lastCmdTime = ts;
		lastCharTime = -1;
		cmdCount++;
	}

	finishAnalysis();
}

void uberFilter::printStats()
{
	VDEBUG("\tTwitchTime:   " << twitchTime)
	VDEBUG("\tAvg nearHopRTT: " << nearHopRtt)
	VDEBUG("\tAvg TypeSpeed:  " << typingSpeed)
	DEBUG("\tGuess RTT:      " << guessRtt)
	DEBUG("\tGuess Num Hops: " << guessNumHops)
	VDEBUG("\tAvg Num Hops:   " << avgNumHops)
}

void uberFilter::updateTypeSpeed()
{
	if (lastCharTime != -1)
	{
		if (interval < speedMultiplier * typingSpeed &&
				interval > typingSpeed / 10)
		{
			totalCharTime += interval;
			charCount++;
			typingSpeed = totalCharTime / charCount;
		}
	}
}

void uberFilter::calculateSuspicion()
{
	if (avgNumHops > upperThreshold)
		suspicion = 100;
	else if (avgNumHops < lowerThreshold)
		suspicion = 0;
	else 
	{
		suspicion = (100*(avgNumHops - lowerThreshold))/(upperThreshold - lowerThreshold);
	}
}

